

上一节中，了解了名称相同的两个函数模板可以共存，即使以相同的参数类型实例化。下面是另一个例子:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{deduce/funcoverload1.hpp}
\begin{lstlisting}[style=styleCXX]
template<typename T>
int f(T)
{
	return 1;
}

template<typename T>
int f(T*)
{
	return 2;
}
\end{lstlisting}

第一个模板中用int*替换T时，得到的函数与在第二个模板中用int替换T得到的函数具有相同的参数(和返回)类型。不仅这些模板可以共存，它们的实例化也可以共存，即使具有相同的参数和返回类型。

下面演示了如何使用显式模板参数语法，调用这两个生成的函数(假设前面对模板进行了声明):

\hspace*{\fill} \\ %插入空行
\noindent
\textit{deduce/funcoverload1.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>
#include "funcoverload1.hpp"
int main()
{
	std::cout << f<int*>((int*)nullptr); // calls f<T>(T)
	std::cout << f<int>((int*)nullptr); // calls f<T>(T*)
}
\end{lstlisting}

程序输出：

\begin{tcblisting}{commandshell={}}
12
\end{tcblisting}

为了说明，这里详细分析一下f<int*>((int*)nullptr)的调用。语法f<int*>()表示用int*替换模板f()的第一个模板参数。这里不止一个模板f()，因此创建了一个重载集，其中包含从模板生成的两个函数:f<int*>(int*)(从第一个模板生成)和f<int*>(int**)(从第二个模板生成)。调用(int*)nullptr的参数类型为int*，这只匹配从第一个模板生成的函数。

对于第二次调用，创建的重载集包含f<int>(int)(从第一个模板生成)和f<int>(int*)(从第二个模板生成)，因此只有第二个模板匹配。

\subsubsubsection{16.2.1\hspace{0.2cm}签名}

如果两个函数有不同的签名，可以在一个程序中共存。函数签名的定义如下所示:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}这个定义与C++标准中给出的定义不同，但其结果等价。
\end{tcolorbox}

\begin{enumerate}
\item 
函数的非限定名(或生成函数模板的名称)

\item 
名称在类或命名空间作用域中，若该名称具有内部链接，则声明为该名称的翻译单元

\item 
函数的const、volatile或const volatile限定符(若具有这样限定符的成员函数)

\item 
函数的\&或\&\&限定符(若具有这样限定符的成员函数)

\item 
函数参数的类型(若函数是由函数模板生成，则指的是替换前的模板参数)

\item 
函数是由函数模板生成的，则包括它的函数返回类型

\item 
若函数是从函数模板中生成，则包括模板参数和模板实参
\end{enumerate}

下面的模板和实例化体可以在一个程序中共存：

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2>
void f1(T1, T2);

template<typename T1, typename T2>
void f1(T2, T1);

template<typename T>
long f2(T);

template<typename T>
char f2(T);
\end{lstlisting}

当定义在相同的作用域中时，实例化会产生重载歧义，所以不能总一起使用。例如，调用f2(42)对于上面声明的模板会产生歧义：

\begin{lstlisting}[style=styleCXX]
#include <iostream>
template<typename T1, typename T2>
void f1(T1, T2)
{
	std::cout << "f1(T1, T2)\n";
}

template<typename T1, typename T2>
void f1(T2, T1)
{
	std::cout << "f1(T2, T1)\n";
}

// fine so far
int main()
{
	f1<char, char>(’a’, ’b’); // ERROR: ambiguous
}
\end{lstlisting}

这里，函数

\begin{lstlisting}[style=styleCXX]
f1<T1 = char, T2 = char>(T1, T2)
\end{lstlisting}

可以与下面函数共存

\begin{lstlisting}[style=styleCXX]
f1<T1 = char, T2 = char>(T2, T1)
\end{lstlisting}

但是重载解析永远无法判断出哪一个更合适。若模板在不同的编译单元中出现，这两个实例化实际上可以在同一个程序中共存(并且，链接器不会抱怨重复的定义，这是因为实例化的签名不同):

\begin{lstlisting}[style=styleCXX]
// translation unit 1:
#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
	std::cout << "f1(T1, T2)\n";
}

void g()
{
	f1<char, char>(’a’, ’b’);
}

// translation unit 2:
#include <iostream>

template<typename T1, typename T2>
void f1(T2, T1)
{
	std::cout << "f1(T2, T1)\n";
}

extern void g(); // defined in translation unit 1

int main()
{
	f1<char, char>(’a’, ’b’);
	g();
}
\end{lstlisting}

该程序有效，其输出如下：

\begin{tcblisting}{commandshell={}}
f1(T2, T1)
f1(T1, T2)
\end{tcblisting}

\subsubsubsection{16.2.2\hspace{0.2cm}重载函数模板的部分排序}

重新考虑前面的例子:发现在替换了给定的模板参数列表(<int*>和<int>)后，重载解析最终调用了正确的函数:

\begin{tcblisting}{commandshell={}}
std::cout << f<int*>((int*)nullptr); // calls f<T>(T)
std::cout << f<int>((int*)nullptr); // calls f<T>(T*)
\end{tcblisting}

因为模板参数推导发挥了作用，即使没有提供显式模板参数，函数也会选中。稍微修改一下前面例子中的main():

\hspace*{\fill} \\ %插入空行
\noindent
\textit{deduce/funcoverload2.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>

template<typename T>
int f(T)
{
	return 1;
}

template<typename T>
int f(T*)
{
	return 2;
}

int main()
{
	std::cout << f(0); // calls f<T>(T)
	std::cout << f(nullptr); // calls f<T>(T)
	std::cout << f((int*)nullptr); // calls f<T>(T*)
}
\end{lstlisting}

考虑第一个调用，f(0):参数类型是int，若用int替换T，其与第一个模板的参数类型匹配。但第二个模板的参数类型始终是指针，在推导之后，会从第一个模板生成的实例是候选，重载解析在这没什么用。

f(nullptr):参数的类型是std::nullptr\_t，也只能匹配第一个模板。

第三个调用(f(int*)nullptr)更有趣:两个模板都成功推导了参数，产生了函数f<int*>(int*)和f<int>(int*)。从重载解析的角度来看，使用int*参数调用这两个函数都是同样的，从而具有歧义(参见附录C)。这里，重载解析标准起了作用:选择从更特化的模板生成的函数。因为，第二个模板更特化，所以输出为

\begin{tcblisting}{commandshell={}}
112
\end{tcblisting}

\subsubsubsection{16.2.3\hspace{0.2cm}正式的排序规则}

上一个示例中，因为第一个模板可以接受任何参数类型，所以第二个模板似乎比第一个模板更特化，而第二个模板只接受指针类型。然而，其他例子并不一定如此直观。接下来，描述了确定参与重载集的函数模板，是否比另一个更特化的过程。这些是部分排序规则:有可能给定两个模板，其中一个比另一个更特化。若重载解析必须在两个这样的模板之间进行选择，则无法做出决定，并且程序包含一个模糊错误。

假设正在比较两个名称相同的函数模板，对于给定的函数调用似乎可行。重载解析的决策如下:

\begin{itemize}
\item 
函数调用参数中没有使用的默认参数和省略号参数在后续将忽略。

\item 
然后，通过如下替换每个模板参数，构造出两个类型列表(对于转换函数模板，是返回类型):

\begin{enumerate}
\item 
将每个模板类型参数替换为一个独特的虚拟类型。

\item 
将每个双重模板参数替换为一个独特的虚拟类模板。

\item 
将每个非类型模板参数替换为创建的相应类型的值。
\end{enumerate}

(虚构出的类型、模板和值在这一上下文中都与任何其他的类型、模板或值不同，这些其他的类型、模板或值要么是开发者使用的，要么是编译器在其他上下文中合成。)

\item 
若第二个模板对第一个合成的参数类型列表中的模板参数推导，实现了精确匹配(反之不行)，那么第一个模板比第二个模板更特化。相反，若第一个模板对第二个合成参数类型列表的模板参数推导成功实现精确匹配(反之不行)，那么第二个模板比第一个模板更特化。
\end{itemize}

将其应用到上一个示例中的两个模板中，从而更直观。两个模板通过替换模板参数合成两个参数类型列表:(A1)和(A2*)(其中A1和A2是唯一的组成类型)。通过将A2*替换为T，第一个模板对第二个参数类型列表的推导成功了，但没有办法使第二个模板的T*匹配第一个列表中的非指针类型A1。因此得出结论，第二个模板比第一个更特化。

考虑一个涉及多个函数参数的例子:

\begin{lstlisting}[style=styleCXX]
template<typename T>
void t(T*, T const* = nullptr, ...);

template<typename T>
void t(T const*, T*, T* = nullptr);

void example(int* p)
{
	t(p, p);
}
\end{lstlisting}

因为实际调用不使用第一个模板的省略号参数，而第二个模板的最后一个参数由其默认参数替代，所以这些参数在部分排序中忽略。注意没有使用第一个模板的默认参数;因此，相应的参数参与了排序。

参数类型的合成列表为(A1*，A1 const*)和(A2 const*，A2*)。与第二个模板相比，(A1*，A1 const*)的模板参数推导成功地将T替换为A1 const，因为需要进行限定调整，所以无法进行精确匹配，使用类型为(A1*，A1 const*)的参数调用T<A1 const>(A1 const*，A1 const*，A1 const* = 0)。类似地，通过从参数类型列表(A2 const*，A2*)推导出第一个模板的参数，也找不到精确匹配。因此，两个模板之间没有排序关系，并且调用存在歧义。

正式的排序规则通常会直接选择函数模板。然而，有时会出现规则与直觉不符的例子。因此，将来可能会修改规则，从而适用于所有例子。

\subsubsubsection{16.2.4\hspace{0.2cm}模板和非模板}

函数模板可以用非模板函数重载。在其他条件相同的情况下，选择要调用的函数时，会首选非模板函数:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/nontmpl1.cpp}
\begin{lstlisting}[style=styleCXX]
#include <string>
#include <iostream>

template<typename T>
std::string f(T)
{
	return "Template";
}

std::string f(int&)
{
	return "Nontemplate";
}

int main()
{
	int x = 7;
	std::cout << f(x) << ’\n’; // prints: Nontemplate
}
\end{lstlisting}

输出为

\begin{tcblisting}{commandshell={}}
Nontemplate
\end{tcblisting}

但当const限定符和引用限定符不同时，重载解析的优先级可能会改变。例如:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/nontmpl2.cpp}
\begin{lstlisting}[style=styleCXX]
#include <string>
#include <iostream>

template<typename T>
std::string f(T&)
{
	return "Template";
}

std::string f(int const&)
{
	return "Nontemplate";
}

int main()
{
	int x = 7;
	std::cout << f(x) << ’\n’; // prints: Template
	int const c = 7;
	std::cout << f(c) << ’\n’; // prints: Nontemplate
}
\end{lstlisting}

程序输出如下:

\begin{tcblisting}{commandshell={}}
Template
Nontemplate
\end{tcblisting}

函数模板f<>(T\&)在传递非常量int时，匹配的更好。对于int来说，实例化的f<>(int\&)比f(int const\&)匹配得更好。因此，区别不仅仅在于是否是函数是模板，可以使用重载解析的一般性规则(见第C.2节)。只有对int const调用f()时，两个签名才具有相同的int const\&类型，因此首选非模板函数。

因此，将成员函数模板声明为

\begin{lstlisting}[style=styleCXX]
template<typename T>
std::string f(T const&)
{
	return "Template";
}
\end{lstlisting}

当成员函数定义接受与复制或移动构造函数相同的参数时，很容易发生意外。例如:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/tmplconstr.cpp}
\begin{lstlisting}[style=styleCXX]
#include <string>
#include <iostream>

class C {
	public:
	C() = default;
	C (C const&) {
		std::cout << "copy constructor\n";
	}
	C (C&&) {
		std::cout << "move constructor\n";
	}
	template<typename T>
	C (T&&) {
		std::cout << "template constructor\n";
	}
};

int main()
{
	C x;
	C x2{x}; // prints: template constructor
	C x3{std::move(x)}; // prints: move constructor
	C const c;
	C x4{c}; // prints: copy constructor
	C x5{std::move(c)}; // prints: template constructor
}
\end{lstlisting}

程序输出如下:

\begin{tcblisting}{commandshell={}}
template constructor
move constructor
copy constructor
template constructor
\end{tcblisting}

因此，成员函数模板比复制构造函数更适合复制C。对于std::move(c)，会产生类型C的const\&\&(这是一种可能的类型，但通常没有有意义的语义)，成员函数模板也比移动构造函数更好。

当成员函数模板可能隐藏复制或移动构造函数时，通常必须部分禁用这些成员函数模板。这在第6.4节中有解释。

\subsubsubsection{16.2.5\hspace{0.2cm}可变参函数模板}

可变参数函数模板(参见第12.4节)在部分排序过程中需要特殊处理，参数包的推导(在第15.5节中描述)会将单个参数与多个参数匹配。这种行为为函数模板排序引入了几种有趣的情况:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/variadicoverload.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>

template<typename T>
int f(T*)
{
	return 1;
}

template<typename... Ts>
int f(Ts...)
{
	return 2;
}

template<typename... Ts>
int f(Ts*...)
{
	return 3;
}

int main()
{
	std::cout << f(0, 0.0); // calls f<>(Ts...)
	std::cout << f((int*)nullptr, (double*)nullptr); // calls f<>(Ts*...)
	std::cout << f((int*)nullptr); // calls f<>(T*)
}
\end{lstlisting}

这个示例的输出是

\begin{tcblisting}{commandshell={}}
231
\end{tcblisting}

第一个调用f(0, 0.0)中，将考虑名为f的函数模板。对于第一个函数模板f(T*)，推导失败的原因有两个:一是无法推导出模板参数T，二是这个非变参函数模板的函数实参比形参多。第二个函数模板f(Ts…)可变:推导将函数参数包(Ts)的模式与两个参数的类型(分别为int和double)进行比较，将Ts推导为(int, double)。对于第三个函数模板f(Ts*…)，将函数参数包Ts*的模式与每个参数类型进行比较。这时推导失败了(不能推导Ts)，只剩下第二个函数模板。所以，这里不需要对函数模板排序。

第二个调用f((int*)nullptr，(double*)nullptr):对于第一个函数模板，推导失败，因为函数实参比形参多，但是对于第二个和第三个模板，推导成功。

\begin{lstlisting}[style=styleCXX]
f<int*,double*>((int*)nullptr, (double*)nullptr) // for second template
\end{lstlisting}

和

\begin{lstlisting}[style=styleCXX]
f<int,double>((int*)nullptr, (double*)nullptr) // for third template
\end{lstlisting}

部分排序考虑第二个和第三个模板，这两个都是可变参数模板:当对可变参数模板应用第16.2.3节描述的正式排序规则时，每个模板参数包将替换为单个组成的类型、类模板或值。第二个和第三个函数模板的合成参数类型分别是A1和A2*，其中A1和A2是合成类型。通过将参数包Ts替换为单元素序列(A2*)，第二个模板针对第三个参数类型列表的推导成功。然而，没有办法使第三个模板的参数包的模式Ts*与非指针类型A1匹配，因此第三个函数模板(接受指针参数)比第二个函数模板(接受任何参数)更特化。

第三个调用，f((int*)nullptr)，引入了一个新的问题:三个函数模板都能成功推导，需要对非变参模板和变参模板进行部分排序。这里，我们比较了第一个和第三个函数模板。合成参数类型是A1*和A2*，其中A1和A2是合成类型。通过将A2替换为T，第一个模板对第三个合成参数列表的推导会成功。另外，通过将单元素序列(A1)替换为参数包Ts，第三个模板对第一个合成参数列表的推导会成功。第一个模板和第三个模板之间的部分排序会产生歧义。然而，有一个特殊规则禁止来自函数参数包的实参(例如，第三个模板的参数包Ts*…)匹配非参数包的形参(第一个模板的形参T*)。因此，第一个模板对第三个合成参数列表的模板推导失败，并且第一个模板比第三个模板更特化。这个特殊规则认为非变参模板(具有固定数量的参数)比变参模板(具有可变数量的参数)更特化。

上面描述的规则同样适用于函数签名中的类型中的包扩展。可以将前面示例中每个函数模板的形参和实参包装到可变参数类模板Tuple中，从而得到一个不涉及函数参数包的示例:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{details/tupleoverload.cpp}
\begin{lstlisting}[style=styleCXX]
#include <iostream>

template<typename... Ts> class Tuple
{
};

template<typename T>
int f(Tuple<T*>)
{
	return 1;
}

template<typename... Ts>
int f(Tuple<Ts...>)
{
	return 2;
}

template<typename... Ts>
int f(Tuple<Ts*...>)
{
	return 3;
}

int main()
{
	std::cout << f(Tuple<int, double>()); // calls f<>(Tuple<Ts...>)
	std::cout << f(Tuple<int*, double*>()); // calls f<>(Tuple<Ts*...>)
	std::cout << f(Tuple<int*>()); // calls f<>(Tuple<T*>)
}
\end{lstlisting}

函数模板排序将Tuple的模板参数中的包扩展为函数参数包，会输出相同的结果:

\begin{tcblisting}{commandshell={}}
231
\end{tcblisting}





















